﻿using System;
using BLToolkit.Reflection;
using System.Collections;

using Convert = BLToolkit.Common.Convert;
using System.Collections.Generic;
using System.Data;

namespace CommonLibrary
{
    public static class JSON
    {
        public static string ToString(object obj)
        {
            JsonWriter writer = new JsonWriter();
            writer.Write(obj);
            return writer.Buffer.ToString();
        }

        public static Object Parse(string json)
        {
            JsonReader reader = new JsonReader(json);
            return reader.Parse();
        }

        public static TResult ToObject<TResult>(string json)
        {
            return (TResult)ToObject(json, typeof(TResult));
        }

        public static object ToObject(string json, Type type)
        {
            JsonReader reader = new JsonReader(json);
            object data = reader.Parse();

            if (type == typeof(object))
                return data;
            else if (type == typeof(DataTable))
                return ConvertToDataTable((object[])data);

            switch (reader.DataType)
            {
                case JsonReader.JsonDataType.Arrary:
                    if (data is object[] &&
                        (type.IsArray || type.GetInterface("IList") != null))
                    {
                        return ConvertToArray((object[])data, type);
                    }

                    break;

                case JsonReader.JsonDataType.Object:
                    if (data is Hashtable)
                    {
                        return ConvertToObject((Hashtable)data, type);
                    }

                    break;

                case JsonReader.JsonDataType.String:
                    if (type == typeof(string))
                        return (string)data;
                    else
                        return System.Convert.ChangeType(data, type);

                case JsonReader.JsonDataType.Number:
                    if (TypeHelper.IsNullableType(type))
                        type = type.GetGenericArguments()[0];
                    if (type == typeof(decimal))
                        return (decimal)data;
                    else if (type == typeof(int))
                        return Convert.ToInt32((decimal)data);
                    else if (type == typeof(uint))
                        return Convert.ToUInt32((decimal)data);
                    else if (type == typeof(double))
                        return Convert.ToDouble((decimal)data);
                    else if (type == typeof(long))
                        return Convert.ToInt64((decimal)data);
                    else if (type == typeof(ulong))
                        return Convert.ToUInt64((decimal)data);
                    else if (type == typeof(string))
                        return ((IFormattable)data).ToString(null, System.Globalization.CultureInfo.InvariantCulture);
                    else
                        return System.Convert.ChangeType(data, type);

                case JsonReader.JsonDataType.Boolean:
                    if (type == typeof(bool))
                        return (bool)data;
                    else if (type == typeof(int))
                        return (int)data;
                    else
                        return System.Convert.ChangeType(data, type);

                default:
                    break;
            }

            return null;
        }

        private static DataTable ConvertToDataTable(object[] args)
        {
            if (args.Length == 0)
                return null;

            DataTable dt = new DataTable();
            Hashtable table = (Hashtable)args[0];
            foreach (DictionaryEntry kv in table)
            {
                dt.Columns.Add((string)kv.Key);
            }

            dt.Rows.Add(table.Values);

            for (int i = 1; i < args.Length; i++)
            {
                dt.Rows.Add(((Hashtable)args[i]).Values);
            }

            return dt;
        }

        private static DataSet ConvertToDataSet()
        {
            throw new NotImplementedException();
        }

        private static object ConvertToObject(Hashtable table, Type type)
        {
            var accessor = TypeAccessor.GetAccessor(type);
            var result = accessor.CreateInstance();
            foreach (MemberAccessor m in accessor)
            {
                if (table.ContainsKey(m.Name))
                {
                    var v = table[m.Name];
                    var mt = TypeHelper.IsNullableType(m.Type) ?
                        m.Type.GetGenericArguments()[0] :
                        m.Type;

                    var code = Type.GetTypeCode(mt);
                    switch (code)
                    {
                        case TypeCode.Boolean:
                            if (v != null)
                                m.SetBoolean(result, (bool)v);
                            else
                                m.SetValue(result, null);
                            break;

                        case TypeCode.SByte:
                            if (v != null)
                                m.SetSByte(result, Convert.ToSByte(v));
                            else
                                m.SetValue(result, null);
                            break;

                        case TypeCode.Byte:
                            if (v != null)
                                m.SetByte(result, Convert.ToByte(v));
                            else
                                m.SetValue(result, null);
                            break;

                        case TypeCode.Char:
                            if (v != null)
                                m.SetChar(result, Convert.ToChar(v));
                            else
                                m.SetValue(result, null);
                            break;

                        case TypeCode.Int16:
                            if (v != null)
                                m.SetInt16(result, Convert.ToInt16(v));
                            else
                                m.SetValue(result, null);
                            break;

                        case TypeCode.Int32:
                            if (v != null)
                                m.SetInt32(result, Convert.ToInt32(v));
                            else
                                m.SetValue(result, null);
                            break;

                        case TypeCode.Int64:
                            if (v != null)
                                m.SetInt64(result, Convert.ToInt64(v));
                            else
                                m.SetValue(result, null);
                            break;

                        case TypeCode.UInt16:
                            if (v != null)
                                m.SetUInt16(result, Convert.ToUInt16(v));
                            else
                                m.SetValue(result, null);
                            break;

                        case TypeCode.UInt32:
                            if (v != null)
                                m.SetUInt32(result, Convert.ToUInt32(v));
                            else
                                m.SetValue(result, null);
                            break;

                        case TypeCode.UInt64:
                            if (v != null)
                                m.SetUInt64(result, Convert.ToUInt64(v));
                            else
                                m.SetValue(result, null);
                            break;

                        case TypeCode.String:
                            if (v != null)
                                m.SetValue(result, Convert.ToString(v));
                            else
                                m.SetValue(result, null);
                            break;

                        case TypeCode.DateTime:
                            if (v != null)
                                m.SetDateTime(result, Convert.ToDateTime(v));
                            else
                                m.SetValue(result, null);
                            break;

                        case TypeCode.Single:
                            if (v != null)
                                m.SetSingle(result, Convert.ToSingle(v));
                            else
                                m.SetValue(result, null);
                            break;

                        case TypeCode.Double:
                            if (v != null)
                                m.SetDouble(result, Convert.ToDouble(v));
                            else
                                m.SetValue(result, null);
                            break;

                        case TypeCode.Object:
                            if (v != null)
                            {
                                if (m.Type.IsArray || m.Type.GetInterface("IList") != null)
                                    m.SetValue(result, ConvertToArray((object[])v, m.Type));
                                else
                                    m.SetValue(result, ConvertToObject((Hashtable)v, m.Type));
                            }
                            else
                            {
                                m.SetValue(result, null);
                            }

                            break;
                    }
                }
            }

            return result;
        }

        private static object ConvertToArray(object[] args, Type type)
        {
            var mt = TypeHelper.GetElementType(type);
            if (type.IsArray)
            {
                if (mt == typeof(object))
                    return args;
                Array result = Array.CreateInstance(mt, args.Length);
                var code = Type.GetTypeCode(mt);
                for (int i = 0; i < args.Length; i++)
                {
                    var v = args[i];
                    switch (code)
                    {
                        case TypeCode.Boolean:
                            if (v != null)
                                result.SetValue((bool)v, i);
                            else
                                result.SetValue(null, i);
                            break;

                        case TypeCode.SByte:
                            if (v != null)
                                result.SetValue(Convert.ToSByte(v), i);
                            else
                                result.SetValue(null, i);
                            break;

                        case TypeCode.Byte:
                            if (v != null)
                                result.SetValue(Convert.ToByte(v), i);
                            else
                                result.SetValue(null, i);
                            break;

                        case TypeCode.Char:
                            if (v != null)
                                result.SetValue(Convert.ToChar(v), i);
                            else
                                result.SetValue(null, i);
                            break;

                        case TypeCode.Int16:
                            if (v != null)
                                result.SetValue(Convert.ToInt16(v), i);
                            else
                                result.SetValue(null, i);
                            break;

                        case TypeCode.Int32:
                            if (v != null)
                                result.SetValue(Convert.ToInt32(v), i);
                            else
                                result.SetValue(null, i);
                            break;

                        case TypeCode.Int64:
                            if (v != null)
                                result.SetValue(Convert.ToInt64(v), i);
                            else
                                result.SetValue(null, i);
                            break;

                        case TypeCode.UInt16:
                            if (v != null)
                                result.SetValue(Convert.ToUInt16(v), i);
                            else
                                result.SetValue(null, i);
                            break;

                        case TypeCode.UInt32:
                            if (v != null)
                                result.SetValue(Convert.ToUInt32(v), i);
                            else
                                result.SetValue(null, i);
                            break;

                        case TypeCode.UInt64:
                            if (v != null)
                                result.SetValue(Convert.ToUInt64(v), i);
                            else
                                result.SetValue(null, i);
                            break;

                        case TypeCode.String:
                            if (v != null)
                                result.SetValue(Convert.ToString(v), i);
                            else
                                result.SetValue(null, i);
                            break;

                        case TypeCode.DateTime:
                            if (v != null)
                                result.SetValue(Convert.ToDateTime(v), i);
                            else
                                result.SetValue(null, i);
                            break;

                        case TypeCode.Single:
                            if (v != null)
                                result.SetValue(Convert.ToSingle(v), i);
                            else
                                result.SetValue(null, i); break;

                        case TypeCode.Double:
                            if (v != null)
                                result.SetValue(Convert.ToDouble(v), i);
                            else
                                result.SetValue(null, i); break;

                        case TypeCode.Object:
                            if (v != null)
                            {
                                if (mt.IsArray || mt.GetInterface("IList") != null)
                                    result.SetValue(ConvertToArray((object[])v, mt), i);
                                else
                                    result.SetValue(ConvertToObject((Hashtable)v, mt), i);
                            }
                            else
                            {
                                result.SetValue(null, i);
                            }
                            break;
                    }
                }

                return result;
            }
            else
            {
                var result = Activator.CreateInstance(type);
                var list = (IList)result;
                if (mt == typeof(object))
                {
                    foreach (var v in args)
                        list.Add(v);
                    return list;
                }

                foreach (var v in args)
                {
                    var code = Type.GetTypeCode(mt);
                    switch (code)
                    {
                        case TypeCode.Boolean:
                            if (v != null)
                                list.Add((bool)v);
                            else
                                list.Add(null);
                            break;

                        case TypeCode.SByte:
                            if (v != null)
                                list.Add(Convert.ToSByte(v));
                            else
                                list.Add(null);
                            break;

                        case TypeCode.Byte:
                            if (v != null)
                                list.Add(Convert.ToByte(v));
                            else
                                list.Add(null);
                            break;

                        case TypeCode.Char:
                            if (v != null)
                                list.Add(Convert.ToChar(v));
                            else
                                list.Add(null);
                            break;

                        case TypeCode.Int16:
                            if (v != null)
                                list.Add(Convert.ToInt16(v));
                            else
                                list.Add(null);
                            break;

                        case TypeCode.Int32:
                            if (v != null)
                                list.Add(Convert.ToInt32(v));
                            else
                                list.Add(null);
                            break;

                        case TypeCode.Int64:
                            if (v != null)
                                list.Add(Convert.ToInt64(v));
                            else
                                list.Add(null);
                            break;

                        case TypeCode.UInt16:
                            if (v != null)
                                list.Add(Convert.ToUInt16(v));
                            else
                                list.Add(null);
                            break;

                        case TypeCode.UInt32:
                            if (v != null)
                                list.Add(Convert.ToUInt32(v));
                            else
                                list.Add(null);
                            break;

                        case TypeCode.UInt64:
                            if (v != null)
                                list.Add(Convert.ToUInt64(v));
                            else
                                list.Add(null);
                            break;

                        case TypeCode.String:
                            if (v != null)
                                list.Add(Convert.ToString(v));
                            else
                                list.Add(null);
                            break;

                        case TypeCode.DateTime:
                            if (v != null)
                                list.Add(Convert.ToDateTime(v));
                            else
                                list.Add(null);
                            break;

                        case TypeCode.Single:
                            if (v != null)
                                list.Add(Convert.ToSingle(v));
                            else
                                list.Add(null); break;

                        case TypeCode.Double:
                            if (v != null)
                                list.Add(Convert.ToDouble(v));
                            else
                                list.Add(null); break;

                        case TypeCode.Object:
                            if (v != null)
                            {
                                if (mt.IsArray || mt.GetInterface("IList") != null)
                                    list.Add(ConvertToArray((object[])v, mt));
                                else
                                    list.Add(ConvertToObject((Hashtable)v, mt));
                            }
                            else
                            {
                                list.Add(null);
                            }
                            break;
                    }
                }

                return result;
            }
        }
    }
}